﻿using GlmNet;
using SharpGL;
using SharpGL.SceneGraph.Assets;
using SharpGL.VertexBuffers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using RDF;

using stepengine = RDF.ifcengine;

#if _WIN64
using int_t = System.Int64;
#else
using int_t = System.Int32;
#endif




namespace DZJIFC.Model
{
    public class _vector3
    {
        public float X;
        public float Y;
        public float Z;

        public _vector3()
        {
            X = 0f;
            Y = 0f;
            Z = 0f;
        }

        public _vector3(float x, float y, float z)
        {
            X = x;
            Y = y;
            Z = z;
        }

        public static void Transform(_vector3 v, _matrix4x4 m, _vector3 result)
        {
            _vector3 tmp = new _vector3();
            tmp.X = (float)(v.X * m._11 + v.Y * m._21 + v.Z * m._31 + m._41);
            tmp.Y = (float)(v.X * m._12 + v.Y * m._22 + v.Z * m._32 + m._42);
            tmp.Z = (float)(v.X * m._13 + v.Y * m._23 + v.Z * m._33 + m._43);

            result.X = tmp.X;
            result.Y = tmp.Y;
            result.Z = tmp.Z;
        }
    }

    public class _matrix4x3
    {
        #region Methods

        public _matrix4x3()
        {
        }

        public static void Multiply(_matrix4x3 m1, _matrix4x3 m2, _matrix4x3 result)
        {
            var temp = new _matrix4x3();
            temp._11 = m1._11 * m2._11 + m1._12 * m2._21 + m1._13 * m2._31;
            temp._12 = m1._11 * m2._12 + m1._12 * m2._22 + m1._13 * m2._32;
            temp._13 = m1._11 * m2._13 + m1._12 * m2._23 + m1._13 * m2._33;

            temp._21 = m1._21 * m2._11 + m1._22 * m2._21 + m1._23 * m2._31;
            temp._22 = m1._21 * m2._12 + m1._22 * m2._22 + m1._23 * m2._32;
            temp._23 = m1._21 * m2._13 + m1._22 * m2._23 + m1._23 * m2._33;

            temp._31 = m1._31 * m2._11 + m1._32 * m2._21 + m1._33 * m2._31;
            temp._32 = m1._31 * m2._12 + m1._32 * m2._22 + m1._33 * m2._32;
            temp._33 = m1._31 * m2._13 + m1._32 * m2._23 + m1._33 * m2._33;

            temp._41 = m1._41 * m2._11 + m1._42 * m2._21 + m1._43 * m2._31 + m2._41;
            temp._42 = m1._41 * m2._12 + m1._42 * m2._22 + m1._43 * m2._32 + m2._42;
            temp._43 = m1._41 * m2._13 + m1._42 * m2._23 + m1._43 * m2._33 + m2._43;

            result._11 = temp._11;
            result._12 = temp._12;
            result._13 = temp._13;

            result._21 = temp._21;
            result._22 = temp._22;
            result._23 = temp._23;

            result._31 = temp._31;
            result._32 = temp._32;
            result._33 = temp._33;

            result._41 = temp._41;
            result._42 = temp._42;
            result._43 = temp._43;
        }

        public static _matrix4x3 Copy(_matrix4x3 m)
        {
            _matrix4x3 result = new _matrix4x3();
            result._11 = m._11;
            result._12 = m._12;
            result._13 = m._13;

            result._21 = m._21;
            result._22 = m._22;
            result._23 = m._23;

            result._31 = m._31;
            result._32 = m._32;
            result._33 = m._33;

            result._41 = m._41;
            result._42 = m._42;
            result._43 = m._43;

            return result;
        }

        #endregion // Methods

        #region Properties

        public double _11 { get; set; } = 1.0;
        public double _12 { get; set; } = 0.0;
        public double _13 { get; set; } = 0.0;

        public double _21 { get; set; } = 0.0;
        public double _22 { get; set; } = 1.0;
        public double _23 { get; set; } = 0.0;

        public double _31 { get; set; } = 0.0;
        public double _32 { get; set; } = 0.0;
        public double _33 { get; set; } = 1.0;

        public double _41 { get; set; } = 0.0;
        public double _42 { get; set; } = 0.0;
        public double _43 { get; set; } = 0.0;

        #endregion // Properties
    }

    public class _matrix4x4
    {
        #region Methods

        public _matrix4x4()
        {
        }

        public static _matrix4x4 To4x4(_matrix4x3 m)
        {
            var result = new _matrix4x4();
            result._11 = m._11;
            result._12 = m._12;
            result._13 = m._13;

            result._21 = m._21;
            result._22 = m._22;
            result._23 = m._23;

            result._31 = m._31;
            result._32 = m._32;
            result._33 = m._33;

            result._41 = m._41;
            result._42 = m._42;
            result._43 = m._43;

            return result;
        }

        public static mat4 ToMat4(_matrix4x4 m)
        {
            var result = mat4.identity();
            result[0, 0] = (float)m._11;
            result[0, 1] = (float)m._12;
            result[0, 2] = (float)m._13;
            result[0, 3] = (float)m._14;

            result[1, 0] = (float)m._21;
            result[1, 1] = (float)m._22;
            result[1, 2] = (float)m._23;
            result[1, 3] = (float)m._24;

            result[2, 0] = (float)m._31;
            result[2, 1] = (float)m._32;
            result[2, 2] = (float)m._33;
            result[2, 3] = (float)m._34;

            result[3, 0] = (float)m._41;
            result[3, 1] = (float)m._42;
            result[3, 2] = (float)m._43;
            result[3, 3] = (float)m._44;

            return result;
        }

        #endregion // Methods

        #region Properties

        public double _11 { get; set; } = 1.0;
        public double _12 { get; set; } = 0.0;
        public double _13 { get; set; } = 0.0;
        public double _14 { get; set; } = 0.0;

        public double _21 { get; set; } = 0.0;
        public double _22 { get; set; } = 1.0;
        public double _23 { get; set; } = 0.0;
        public double _24 { get; set; } = 0.0;

        public double _31 { get; set; } = 0.0;
        public double _32 { get; set; } = 0.0;
        public double _33 { get; set; } = 1.0;
        public double _34 { get; set; } = 0.0;

        public double _41 { get; set; } = 0.0;
        public double _42 { get; set; } = 0.0;
        public double _43 { get; set; } = 0.0;
        public double _44 { get; set; } = 1.0;

        #endregion // Properties
    }

    public static class _oglUtils
    {
        public static uint GetVerticesCountLimit(uint iVertexLengthBytes)
        {
            return uint.MaxValue / iVertexLengthBytes;
        }

        public static uint GetIndicesCountLimit()
        {
            return 64800;
        }
    }

    public class _primitives
    {
        public _primitives(long iStartIndex, long iIndicesCount)
        {
            StartIndex = iStartIndex;
            IndicesCount = iIndicesCount;
        }

        public long StartIndex { get; set; }
        public long IndicesCount { get; private set; }
    };

    public class _cohort
    {
        public _cohort()
        { }

        public List<uint> Indices { get; set; } = new List<uint>();

        public uint IBO { get; set; }
        public uint IBOOffset { get; set; }
    }

    public class _vertices_cohort
    {
        public _vertices_cohort()
        {}

        public void Initialize(OpenGL gl)
        {
            VertexBufferArray = new VertexBufferArray();
            VertexBufferArray.Create(gl);
            VertexBufferArray.Bind(gl);

            VerticesBuffer = new VertexBuffer();
            VerticesBuffer.Create(gl);
            VerticesBuffer.Bind(gl);
            VerticesBuffer.SetData(gl, 0, Vertices.ToArray(), false, 3);

            NormalsBuffer = new VertexBuffer();
            NormalsBuffer.Create(gl);
            NormalsBuffer.Bind(gl);
            NormalsBuffer.SetData(gl, 1, Normals.ToArray(), false, 3);

            IndicesBuffer = new IndexBuffer();
            IndicesBuffer.Create(gl);
            IndicesBuffer.Bind(gl);
            IndicesBuffer.SetData(gl, Indices);

            VertexBufferArray.Unbind(gl);

            Vertices = null;
            Normals = null;
            Indices = null;
        }

        public void Delete(OpenGL gl)
        {
            if (VertexBufferArray != null)
            {
                VerticesBuffer.Delete(gl);
                NormalsBuffer.Delete(gl);
                IndicesBuffer.Delete(gl);

                VertexBufferArray.Delete(gl); 
            }
        }

        public float[] Vertices { get; set; }
        private VertexBuffer VerticesBuffer {  get; set; }
        public float[] Normals { get; set; }
        private VertexBuffer NormalsBuffer { get; set; }
        public ushort[] Indices { get; set; }
        private IndexBuffer IndicesBuffer { get; set; }
        public int IndicesCount { get; set; }
        public _material Material { get; set; }
        public VertexBufferArray VertexBufferArray { get; set; }
    }

    public class _face : _primitives
    {
        public _face(long iIndex, long iStartIndex, long iIndicesCount)
            : base(iStartIndex, iIndicesCount)
        {
            Index = iIndex;
        }
        public long Index { get; private set; }
    };

    public class _cohortWithMaterial : _cohort
    {
        public _cohortWithMaterial(_material material)
            : base()
        {
            this.Material = material;
        }

        public List<_face> Faces { get; set; } = new List<_face>();
        public _material Material { get; private set; }
    };

    public class _geometry
    {
        #region Fields

        // Cache
        protected Dictionary<_material, List<_face>> _dicMaterial2ConcFaces = new Dictionary<_material, List<_face>>();
        protected Dictionary<_material, List<_face>> _dicMaterial2ConcFaceLines = new Dictionary<_material, List<_face>>();
        protected Dictionary<_material, List<_face>> _dicMaterial2ConcFacePoints = new Dictionary<_material, List<_face>>();

        #endregion // Fields

        #region Methods

        public _geometry() 
        { 
        }

        public void CalculateMinMax(
            ref float fXmin, ref float fXmax,
            ref float fYmin, ref float fYmax,
            ref float fZmin, ref float fZmax)
        {
            if (!HasGeometry)
            {
                return;
            }

            uint VERTEX_LENGTH = GetVertexLength();

            if (Triangles.Count > 0)
            {
                foreach (var primitive in Triangles)
                {
                    for (var iIndex = primitive.StartIndex;
                        iIndex < primitive.StartIndex + primitive.IndicesCount;
                        iIndex++)
                    {
                        fXmin = float.Min(fXmin, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 0]);
                        fXmax = float.Max(fXmax, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 0]);
                        fYmin = float.Min(fYmin, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 1]);
                        fYmax = float.Max(fYmax, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 1]);
                        fZmin = float.Min(fZmin, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 2]);
                        fZmax = float.Max(fZmax, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 2]);
                    }
                }
            }

            if (ConcFacePolygons.Count > 0)
            {
                foreach (var primitive in ConcFacePolygons)
                {
                    for (var iIndex = primitive.StartIndex;
                        iIndex < primitive.StartIndex + primitive.IndicesCount;
                        iIndex++)
                    {
                        fXmin = float.Min(fXmin, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 0]);
                        fXmax = float.Max(fXmax, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 0]);
                        fYmin = float.Min(fYmin, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 1]);
                        fYmax = float.Max(fYmax, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 1]);
                        fZmin = float.Min(fZmin, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 2]);
                        fZmax = float.Max(fZmax, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 2]);
                    }
                }
            }

            if (Lines.Count > 0)
            {
                foreach (var primitive in Lines)
                {
                    for (var iIndex = primitive.StartIndex;
                        iIndex < primitive.StartIndex + primitive.IndicesCount;
                        iIndex++)
                    {
                        fXmin = float.Min(fXmin, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 0]);
                        fXmax = float.Max(fXmax, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 0]);
                        fYmin = float.Min(fYmin, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 1]);
                        fYmax = float.Max(fYmax, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 1]);
                        fZmin = float.Min(fZmin, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 2]);
                        fZmax = float.Max(fZmax, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 2]);
                    }
                }
            }

            if (Points.Count > 0)
            {
                foreach (var primitive in Points)
                {
                    for (var iIndex = primitive.StartIndex;
                        iIndex < primitive.StartIndex + primitive.IndicesCount;
                        iIndex++)
                    {
                        fXmin = float.Min(fXmin, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 0]);
                        fXmax = float.Max(fXmax, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 0]);
                        fYmin = float.Min(fYmin, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 1]);
                        fYmax = float.Max(fYmax, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 1]);
                        fZmin = float.Min(fZmin, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 2]);
                        fZmax = float.Max(fZmax, Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 2]);
                    }
                }
            }
        }

        public void CalculateMinMax(
            _matrix4x4 transformation,
            ref float fXmin, ref float fXmax,
            ref float fYmin, ref float fYmax,
            ref float fZmin, ref float fZmax)
        {
            if (transformation == null)
            {
                CalculateMinMax(
                    ref fXmin, ref fXmax,
                    ref fYmin, ref fYmax,
                    ref fZmin, ref fZmax);

                return; 
            }

            if (!HasGeometry)
            {
                return;
            }

            uint VERTEX_LENGTH = GetVertexLength();

            if (Triangles.Count > 0)
            {
                foreach (var primitive in Triangles)
                {
                    for (var iIndex = primitive.StartIndex;
                        iIndex < primitive.StartIndex + primitive.IndicesCount;
                        iIndex++)
                    {
                        _vector3 point = new _vector3(
                            Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 0],
                            Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 1],
                            Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 2]);
                        _vector3.Transform(point, transformation, point);

                        fXmin = float.Min(fXmin, point.X);
                        fXmax = float.Max(fXmax, point.X);
                        fYmin = float.Min(fYmin, point.Y);
                        fYmax = float.Max(fYmax, point.Y);
                        fZmin = float.Min(fZmin, point.Z);
                        fZmax = float.Max(fZmax, point.Z);
                    }
                }
            }

            if (ConcFacePolygons.Count > 0)
            {
                foreach (var primitive in ConcFacePolygons)
                {
                    for (var iIndex = primitive.StartIndex;
                        iIndex < primitive.StartIndex + primitive.IndicesCount;
                        iIndex++)
                    {
                        _vector3 point = new _vector3(
                            Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 0],
                            Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 1],
                            Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 2]);
                        _vector3.Transform(point, transformation, point);

                        fXmin = float.Min(fXmin, point.X);
                        fXmax = float.Max(fXmax, point.X);
                        fYmin = float.Min(fYmin, point.Y);
                        fYmax = float.Max(fYmax, point.Y);
                        fZmin = float.Min(fZmin, point.Z);
                        fZmax = float.Max(fZmax, point.Z);
                    }
                }
            }

            if (Lines.Count > 0)
            {
                foreach (var primitive in Lines)
                {
                    for (var iIndex = primitive.StartIndex;
                        iIndex < primitive.StartIndex + primitive.IndicesCount;
                        iIndex++)
                    {
                        _vector3 point = new _vector3(
                            Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 0],
                            Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 1],
                            Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 2]);
                        _vector3.Transform(point, transformation, point);

                        fXmin = float.Min(fXmin, point.X);
                        fXmax = float.Max(fXmax, point.X);
                        fYmin = float.Min(fYmin, point.Y);
                        fYmax = float.Max(fYmax, point.Y);
                        fZmin = float.Min(fZmin, point.Z);
                        fZmax = float.Max(fZmax, point.Z);
                    }
                }
            }

            if (Points.Count > 0)
            {
                foreach (var primitive in Points)
                {
                    for (var iIndex = primitive.StartIndex;
                        iIndex < primitive.StartIndex + primitive.IndicesCount;
                        iIndex++)
                    {
                        _vector3 point = new _vector3(
                            Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 0],
                            Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 1],
                            Vertices[(Indices[iIndex] * VERTEX_LENGTH) + 2]);
                        _vector3.Transform(point, transformation, point);

                        fXmin = float.Min(fXmin, point.X);
                        fXmax = float.Max(fXmax, point.X);
                        fYmin = float.Min(fYmin, point.Y);
                        fYmax = float.Max(fYmax, point.Y);
                        fZmin = float.Min(fZmin, point.Z);
                        fZmax = float.Max(fZmax, point.Z);
                    }
                }
            }
        }

        public virtual void Scale(float fScaleFactor)
        {
            if (!HasGeometry)
            {
                return;
            }

            uint VERTEX_LENGTH = GetVertexLength();

            for (var iVertex = 0; iVertex < Vertices.Length / VERTEX_LENGTH; iVertex++)
            {
                Vertices[(iVertex * VERTEX_LENGTH) + 0] /= fScaleFactor;
                Vertices[(iVertex * VERTEX_LENGTH) + 1] /= fScaleFactor;
                Vertices[(iVertex * VERTEX_LENGTH) + 2] /= fScaleFactor;
            }
        }

        public void BuildCohorts()
        {
            BuildConcFacesCohorts(_dicMaterial2ConcFaces);
            BuildConcFacePolygonsCohorts();
            BuildLinesCohorts(_dicMaterial2ConcFaceLines);
            BuildPointsCohorts(_dicMaterial2ConcFacePoints);
        }

        protected void BuildConcFacesCohorts(Dictionary<_material, List<_face>> materials)
        {
            uint INDICES_MAX_COUNT = _oglUtils.GetIndicesCountLimit();

            var concFacesCohorts = new List<_cohortWithMaterial>();
            foreach (var material in materials)
            {
                _cohortWithMaterial pCohort = null;

                foreach (var concFace in material.Value)
                {
                    long iStartIndex = concFace.StartIndex;
                    long iIndicesCount = concFace.IndicesCount;

                    // Split the conceptual face - isolated case
                    if (iIndicesCount > INDICES_MAX_COUNT)
                    {
                        while (iIndicesCount > INDICES_MAX_COUNT)
                        {
                            var pNewCohort = new _cohortWithMaterial(material.Key);

                            for (long iIndex = iStartIndex;
                                iIndex < iStartIndex + iIndicesCount;
                                iIndex++)
                            {
                                pNewCohort.Indices.Add((uint)Indices[iIndex]);
                            }

                            concFacesCohorts.Add(pNewCohort);

                            // Update Conceptual face start index
                            concFace.StartIndex = 0;

                            // Conceptual faces
                            pNewCohort.Faces.Add(concFace);

                            iIndicesCount -= INDICES_MAX_COUNT;
                            iStartIndex += INDICES_MAX_COUNT;
                        }

                        if (iIndicesCount > 0)
                        {
                            var pNewCohort = new _cohortWithMaterial(material.Key);

                            for (long iIndex = iStartIndex;
                                iIndex < iStartIndex + INDICES_MAX_COUNT;
                                iIndex++)
                            {
                                pNewCohort.Indices.Add((uint)Indices[iIndex]);
                            }

                            // Update Conceptual face start index
                            concFace.StartIndex = 0;

                            // Conceptual faces
                            pNewCohort.Faces.Add(concFace);
                        }

                        continue;
                    } // if (iIndicesCount > INDICES_MAX_COUNT)

                    // Create cohort
                    if (pCohort == null)
                    {
                        pCohort = new _cohortWithMaterial(material.Key);

                        concFacesCohorts.Add(pCohort);
                    }

                    // Check the limit
                    if (pCohort.Indices.Count + iIndicesCount > INDICES_MAX_COUNT)
                    {
                        pCohort = new _cohortWithMaterial(material.Key);

                        concFacesCohorts.Add(pCohort);
                    }

                    // Update Conceptual face start index
                    concFace.StartIndex = pCohort.Indices.Count;

                    for (long iIndex = iStartIndex;
                        iIndex < iStartIndex + iIndicesCount;
                        iIndex++)
                    {
                        pCohort.Indices.Add((uint)Indices[iIndex]);
                    }

                    // Conceptual faces
                    pCohort.Faces.Add(concFace);
                } // foreach (var concFace in ...
            } // foreach (var material in ...

            /* Merge the Cohorts */

            foreach (var cohort in concFacesCohorts)
            {
                if (cohort.Indices.Count > INDICES_MAX_COUNT)
                {
                    Debug.Assert(false); //#todo

                    continue;
                }

                var lsVertices = new List<float>();
                var lsNormals = new List<float>();
                var lsIndices = new List<ushort>();
                for (int iIndex = 0; iIndex < cohort.Indices.Count; iIndex++)
                {
                    lsVertices.Add(Vertices[(GetVertexLength() * cohort.Indices[iIndex]) + 0]);
                    lsVertices.Add(Vertices[(GetVertexLength() * cohort.Indices[iIndex]) + 1]);
                    lsVertices.Add(Vertices[(GetVertexLength() * cohort.Indices[iIndex]) + 2]);

                    lsNormals.Add(Vertices[(GetVertexLength() * cohort.Indices[iIndex]) + 3]);
                    lsNormals.Add(Vertices[(GetVertexLength() * cohort.Indices[iIndex]) + 4]);
                    lsNormals.Add(Vertices[(GetVertexLength() * cohort.Indices[iIndex]) + 5]);

                    lsIndices.Add((ushort)lsIndices.Count);
                }

                var verticesCohort = new _vertices_cohort();
                verticesCohort.Vertices = lsVertices.ToArray();
                verticesCohort.Normals = lsNormals.ToArray();
                verticesCohort.Indices = lsIndices.ToArray();
                verticesCohort.IndicesCount = cohort.Indices.Count;
                verticesCohort.Material = cohort.Material;
                ConcFacesVerticesCohorts.Add(verticesCohort);
            }

            materials.Clear();
        }

        protected void BuildConcFacePolygonsCohorts()
        {
            uint INDICES_MAX_COUNT = _oglUtils.GetIndicesCountLimit();

            var concFacePolygonsCohorts = new List<_cohort>();

            // Use the last cohort (if any)
            _cohort pCohort = concFacePolygonsCohorts.Count == 0 ? null : concFacePolygonsCohorts.Last();

            // Create the cohort
            if (pCohort == null)
            {
                pCohort = new _cohort();
                concFacePolygonsCohorts.Add(pCohort);
            }

            foreach (var concFace in ConcFacePolygons)
            {
                long iStartIndex = concFace.StartIndex;
                long iIndicesCount = concFace.IndicesCount;

                // Split the conceptual face - isolated case
                if (iIndicesCount > INDICES_MAX_COUNT / 2)
                {
                    while (iIndicesCount > INDICES_MAX_COUNT / 2)
                    {
                        var pNewCohort = new _cohort();
                        concFacePolygonsCohorts.Add(pNewCohort);

                        for (long iIndex = iStartIndex;
                            iIndex < iStartIndex + INDICES_MAX_COUNT / 2;
                            iIndex += 2)
                        {
                            pNewCohort.Indices.Add((uint)Indices[iIndex + 0]);
                            pNewCohort.Indices.Add((uint)Indices[iIndex + 1]);
                        }

                        iIndicesCount -= INDICES_MAX_COUNT / 2;
                        iStartIndex += INDICES_MAX_COUNT / 2;
                    }

                    if (iIndicesCount > 0)
                    {
                        var pNewCohort = new _cohort();
                        concFacePolygonsCohorts.Add(pNewCohort);

                        for (long iIndex = iStartIndex;
                            iIndex < iStartIndex + iIndicesCount;
                            iIndex += 2)
                        {
                            pNewCohort.Indices.Add((uint)Indices[iIndex + 0]);
                            pNewCohort.Indices.Add((uint)Indices[iIndex + 1]);
                        }
                    }

                    continue;
                } // if (iIndicesCount > INDICES_MAX_COUNT / 2)

                if ((pCohort.Indices.Count + iIndicesCount) > INDICES_MAX_COUNT)
                {
                    pCohort = new _cohort();
                    concFacePolygonsCohorts.Add(pCohort);
                }

                for (long iIndex = iStartIndex;
                        iIndex < iStartIndex + iIndicesCount;
                        iIndex += 2)
                {
                    pCohort.Indices.Add((uint)Indices[iIndex + 0]);
                    pCohort.Indices.Add((uint)Indices[iIndex + 1]);
                }
            } // foreach (var concFace in ...

            /* Merge the Cohorts */

            uint iVerticesCount = 0;
            var lsCohorts = new List<_cohort>();
            foreach (var cohort in concFacePolygonsCohorts)
            {
                if ((iVerticesCount + cohort.Indices.Count) > INDICES_MAX_COUNT)
                {
                    int iIndicesCount = 0;
                    var lsVertices = new List<float>();
                    var lsNormals = new List<float>();
                    var lsIndices = new List<ushort>();
                    foreach (var item in lsCohorts)
                    {
                        for (int iIndex = 0; iIndex < item.Indices.Count; iIndex++)
                        {
                            lsVertices.Add(Vertices[(GetVertexLength() * item.Indices[iIndex]) + 0]);
                            lsVertices.Add(Vertices[(GetVertexLength() * item.Indices[iIndex]) + 1]);
                            lsVertices.Add(Vertices[(GetVertexLength() * item.Indices[iIndex]) + 2]);

                            lsNormals.Add(Vertices[(GetVertexLength() * item.Indices[iIndex]) + 3]);
                            lsNormals.Add(Vertices[(GetVertexLength() * item.Indices[iIndex]) + 4]);
                            lsNormals.Add(Vertices[(GetVertexLength() * item.Indices[iIndex]) + 5]);

                            lsIndices.Add((ushort)lsIndices.Count);
                        }

                        iIndicesCount += item.Indices.Count;
                    }

                    var verticesCohort = new _vertices_cohort();
                    verticesCohort.Vertices = lsVertices.ToArray();
                    verticesCohort.Normals = lsNormals.ToArray();
                    verticesCohort.Indices = lsIndices.ToArray();
                    verticesCohort.IndicesCount = iIndicesCount;
                    ConcFacePolygonsVerticesCohorts.Add(verticesCohort);

                    iVerticesCount = 0;
                    lsCohorts.Clear();
                }

                iVerticesCount += (uint)cohort.Indices.Count;
                lsCohorts.Add(cohort);
            }

            if (iVerticesCount > 0)
            {
                int iIndicesCount = 0;
                var lsVertices = new List<float>();
                var lsNormals = new List<float>();
                var lsIndices = new List<ushort>();
                foreach (var item in lsCohorts)
                {
                    for (int iIndex = 0; iIndex < item.Indices.Count; iIndex++)
                    {
                        lsVertices.Add(Vertices[(GetVertexLength() * item.Indices[iIndex]) + 0]);
                        lsVertices.Add(Vertices[(GetVertexLength() * item.Indices[iIndex]) + 1]);
                        lsVertices.Add(Vertices[(GetVertexLength() * item.Indices[iIndex]) + 2]);

                        lsNormals.Add(Vertices[(GetVertexLength() * item.Indices[iIndex]) + 3]);
                        lsNormals.Add(Vertices[(GetVertexLength() * item.Indices[iIndex]) + 4]);
                        lsNormals.Add(Vertices[(GetVertexLength() * item.Indices[iIndex]) + 5]);

                        lsIndices.Add((ushort)lsIndices.Count);
                    }

                    iIndicesCount += item.Indices.Count;
                }

                var verticesCohort = new _vertices_cohort();
                verticesCohort.Vertices = lsVertices.ToArray();
                verticesCohort.Normals = lsNormals.ToArray();
                verticesCohort.Indices = lsIndices.ToArray();
                verticesCohort.IndicesCount = iIndicesCount;
                ConcFacePolygonsVerticesCohorts.Add(verticesCohort);

                iVerticesCount = 0;
                lsCohorts.Clear();
            } // if (iVerticesCount > 0)
        }

        protected void BuildLinesCohorts(Dictionary<_material, List<_face>> materials)
        {
            uint INDICES_MAX_COUNT = _oglUtils.GetIndicesCountLimit();

            var linesCohorts = new List<_cohortWithMaterial>();
            foreach (var material in materials)
            {
                _cohortWithMaterial pCohort = null;

                foreach (var concFace in material.Value)
                {
                    long iStartIndex = concFace.StartIndex;
                    long iIndicesCount = concFace.IndicesCount;

                    // Split the conceptual face - isolated case
                    if (iIndicesCount > INDICES_MAX_COUNT)
                    {
                        while (iIndicesCount > INDICES_MAX_COUNT)
                        {
                            var pNewCohort = new _cohortWithMaterial(material.Key);

                            for (long iIndex = iStartIndex;
                                iIndex < iStartIndex + INDICES_MAX_COUNT;
                                iIndex++)
                            {
                                pNewCohort.Indices.Add((uint)Indices[iIndex]);
                            }

                            linesCohorts.Add(pNewCohort);

                            // Update Conceptual face start index
                            concFace.StartIndex = 0;

                            // Conceptual faces
                            pNewCohort.Faces.Add(concFace);

                            iIndicesCount -= INDICES_MAX_COUNT;
                            iStartIndex += INDICES_MAX_COUNT;
                        }

                        if (iIndicesCount > 0)
                        {
                            var pNewCohort = new _cohortWithMaterial(material.Key);

                            for (long iIndex = iStartIndex;
                                iIndex < iStartIndex + iIndicesCount;
                                iIndex++)
                            {
                                pNewCohort.Indices.Add((uint)Indices[iIndex]);
                            }

                            // Update Conceptual face start index
                            concFace.StartIndex = 0;

                            // Conceptual faces
                            pNewCohort.Faces.Add(concFace);
                        }

                        continue;
                    } // if (iIndicesCount > INDICES_MAX_COUNT)

                    // Create cohort
                    if (pCohort == null)
                    {
                        pCohort = new _cohortWithMaterial(material.Key);

                        linesCohorts.Add(pCohort);
                    }

                    // Check the limit
                    if (pCohort.Indices.Count + iIndicesCount > INDICES_MAX_COUNT)
                    {
                        pCohort = new _cohortWithMaterial(material.Key);

                        linesCohorts.Add(pCohort);
                    }

                    // Update Conceptual face start index
                    concFace.StartIndex = pCohort.Indices.Count;

                    for (long iIndex = iStartIndex;
                        iIndex < iStartIndex + iIndicesCount;
                        iIndex++)
                    {
                        pCohort.Indices.Add((uint)Indices[iIndex]);
                    }

                    // Conceptual faces
                    pCohort.Faces.Add(concFace);
                } // foreach (var concFace in ...
            } // foreach (var material in ...

            /* Merge the Cohorts */

            foreach (var cohort in linesCohorts)
            {
                if (cohort.Indices.Count > INDICES_MAX_COUNT)
                {
                    Debug.Assert(false); //#todo

                    continue;
                }

                var lsVertices = new List<float>();
                var lsNormals = new List<float>();
                var lsIndices = new List<ushort>();
                for (int iIndex = 0; iIndex < cohort.Indices.Count; iIndex++)
                {
                    lsVertices.Add(Vertices[(GetVertexLength() * cohort.Indices[iIndex]) + 0]);
                    lsVertices.Add(Vertices[(GetVertexLength() * cohort.Indices[iIndex]) + 1]);
                    lsVertices.Add(Vertices[(GetVertexLength() * cohort.Indices[iIndex]) + 2]);

                    lsNormals.Add(Vertices[(GetVertexLength() * cohort.Indices[iIndex]) + 3]);
                    lsNormals.Add(Vertices[(GetVertexLength() * cohort.Indices[iIndex]) + 4]);
                    lsNormals.Add(Vertices[(GetVertexLength() * cohort.Indices[iIndex]) + 5]);

                    lsIndices.Add((ushort)lsIndices.Count);
                }

                var verticesCohort = new _vertices_cohort();
                verticesCohort.Vertices = lsVertices.ToArray();
                verticesCohort.Normals = lsNormals.ToArray();
                verticesCohort.Indices = lsIndices.ToArray();
                verticesCohort.IndicesCount = cohort.Indices.Count;
                verticesCohort.Material = cohort.Material;
                LinesVerticesCohorts.Add(verticesCohort);
            }

            materials.Clear();
        }

        protected void BuildPointsCohorts(Dictionary<_material, List<_face>> materials)
        {
            uint INDICES_MAX_COUNT = _oglUtils.GetIndicesCountLimit();

            var pointsCohorts = new List<_cohortWithMaterial>();
            foreach (var material in materials)
            {
                _cohortWithMaterial pCohort = null;

                foreach (var concFace in material.Value)
                {
                    long iStartIndex = concFace.StartIndex;
                    long iIndicesCount = concFace.IndicesCount;

                    // Split the conceptual face - isolated case
                    if (iIndicesCount > INDICES_MAX_COUNT)
                    {
                        while (iIndicesCount > INDICES_MAX_COUNT)
                        {
                            var pNewCohort = new _cohortWithMaterial(material.Key);

                            for (long iIndex = iStartIndex;
                                iIndex < iStartIndex + INDICES_MAX_COUNT;
                                iIndex++)
                            {
                                pNewCohort.Indices.Add((uint)Indices[iIndex]);
                            }

                            pointsCohorts.Add(pNewCohort);

                            // Update Conceptual face start index
                            concFace.StartIndex = 0;

                            // Conceptual faces
                            pNewCohort.Faces.Add(concFace);

                            iIndicesCount -= INDICES_MAX_COUNT;
                            iStartIndex += INDICES_MAX_COUNT;
                        }

                        if (iIndicesCount > 0)
                        {
                            var pNewCohort = new _cohortWithMaterial(material.Key);

                            for (long iIndex = iStartIndex;
                                iIndex < iStartIndex + iIndicesCount;
                                iIndex++)
                            {
                                pNewCohort.Indices.Add((uint)Indices[iIndex]);
                            }

                            // Update Conceptual face start index
                            concFace.StartIndex = 0;

                            // Conceptual faces
                            pNewCohort.Faces.Add(concFace);
                        }

                        continue;
                    } // if (iIndicesCount > INDICES_MAX_COUNT)

                    // Create cohort
                    if (pCohort == null)
                    {
                        pCohort = new _cohortWithMaterial(material.Key);

                        pointsCohorts.Add(pCohort);
                    }

                    // Check the limit
                    if (pCohort.Indices.Count + iIndicesCount > INDICES_MAX_COUNT)
                    {
                        pCohort = new _cohortWithMaterial(material.Key);

                        pointsCohorts.Add(pCohort);
                    }

                    // Update Conceptual face start index
                    concFace.StartIndex = pCohort.Indices.Count;

                    for (long iIndex = iStartIndex;
                        iIndex < iStartIndex + iIndicesCount;
                        iIndex++)
                    {
                        pCohort.Indices.Add((uint)Indices[iIndex]);
                    }

                    // Conceptual faces
                    pCohort.Faces.Add(concFace);
                } // foreach (var concFace in ...
            } // foreach (var material in ...

            /* Merge the Cohorts */

            foreach (var cohort in pointsCohorts)
            {
                if (cohort.Indices.Count > INDICES_MAX_COUNT)
                {
                    Debug.Assert(false); //#todo

                    continue;
                }

                var lsVertices = new List<float>();
                var lsNormals = new List<float>();
                var lsIndices = new List<ushort>();
                for (int iIndex = 0; iIndex < cohort.Indices.Count; iIndex++)
                {
                    lsVertices.Add(Vertices[(GetVertexLength() * cohort.Indices[iIndex]) + 0]);
                    lsVertices.Add(Vertices[(GetVertexLength() * cohort.Indices[iIndex]) + 1]);
                    lsVertices.Add(Vertices[(GetVertexLength() * cohort.Indices[iIndex]) + 2]);

                    lsNormals.Add(Vertices[(GetVertexLength() * cohort.Indices[iIndex]) + 3]);
                    lsNormals.Add(Vertices[(GetVertexLength() * cohort.Indices[iIndex]) + 4]);
                    lsNormals.Add(Vertices[(GetVertexLength() * cohort.Indices[iIndex]) + 5]);

                    lsIndices.Add((ushort)lsIndices.Count);
                }

                var verticesCohort = new _vertices_cohort();
                verticesCohort.Vertices = lsVertices.ToArray();
                verticesCohort.Normals = lsNormals.ToArray();
                verticesCohort.Indices = lsIndices.ToArray();
                verticesCohort.IndicesCount = cohort.Indices.Count;
                verticesCohort.Material = cohort.Material;
                PointsVerticesCohorts.Add(verticesCohort);
            }

            materials.Clear();
        }

        protected virtual void SetFormat(int_t iModel)
        {
            int_t setting = 0, mask = 0;
            mask += stepengine.flagbit2;       //    PRECISION (32/64 bit)
            mask += stepengine.flagbit3;       //	INDEX ARRAY (32/64 bit)
            mask += stepengine.flagbit5;       //    NORMALS
            mask += stepengine.flagbit8;       //    TRIANGLES
            mask += stepengine.flagbit9;       //    LINES
            mask += stepengine.flagbit10;      //    POINTS
            mask += stepengine.flagbit13;      //    CONCEPTUAL FACE POLYGONS
            mask += stepengine.flagbit15;      //    ADVANCED NORMALS

            setting += 0;                         //    SINGLE PRECISION (float)
            setting += 0;                         //    32 BIT INDEX ARRAY (Int32)
            setting += stepengine.flagbit5;    //    NORMALS ON
            setting += stepengine.flagbit8;    //    TRIANGLES ON
            setting += stepengine.flagbit9;    //    LINES ON
            setting += stepengine.flagbit10;   //    POINTS ON
            setting += stepengine.flagbit13;   //    CONCEPTUAL FACE POLYGONS ON
            setting += stepengine.flagbit15;   //    ADVANCED NORMALS

            RDF.engine.SetFormat(iModel, (ulong)setting, (ulong)mask);
            RDF.engine.SetBehavior(iModel, 2048 + 4096, 2048 + 4096);
            stepengine.setSegmentation(iModel, 16, 0.0);
        }

        protected int_t GetModel()
        {
            return stepengine.sdaiGetInstanceModel(Instance);
        }

        protected uint GetVertexLength()
        {
            return (uint)(RDF.engine.SetFormat(GetModel(), 0, 0) / sizeof(float));
        }

        #endregion // Methods

        #region Properties

        // Metadata
        public int_t Instance { get; protected set; }
        public string Entity { get; protected set; }
        public string Name { get; protected set; } = "NA";
        public string UniqueName { get; protected set; } = string.Empty;
        
        // Geometry
        public float[] Vertices { get; protected set; }
        public int[] Indices { get; protected set; }
        public long ConceptualFacesCount { get; protected set; }
        public bool HasGeometry => (Vertices != null && Vertices.Length > 0) && (Indices != null && Indices.Length > 0);

        // Primitives (Cache)
        protected List<_primitives> Triangles { get; set; } = new List<_primitives>();
        protected List<_primitives> ConcFacePolygons { get; set; } = new List<_primitives>();
        protected List<_primitives> Lines { get; set; } = new List<_primitives>();
        protected List<_primitives> Points { get; set; } = new List<_primitives>();

        // Cohorts (Cache)
        public List<_vertices_cohort> ConcFacesVerticesCohorts { get; set; } = new List<_vertices_cohort>();
        public List<_vertices_cohort> ConcFacePolygonsVerticesCohorts { get; set; } = new List<_vertices_cohort>();
        public List<_vertices_cohort> LinesVerticesCohorts { get; set; } = new List<_vertices_cohort>();
        public List<_vertices_cohort> PointsVerticesCohorts { get; set; } = new List<_vertices_cohort>();

        #endregion // Properties
    }
}
